﻿using System;
using System.Collections.Generic;
using SimpleServiceBus.Bus.MessageManagement.Serialization;
using SimpleServiceBus.Endpoint;
using SimpleServiceBus.Utilities;
using SimpleServiceBus.Utilities.ObjectBuilder;
using WorkerThread=SimpleServiceBus.Utilities.WorkerThread;

namespace SimpleServiceBus.Bus.Transport
{
    public abstract class BaseTransport: EndpointServiceBase,ITransport
    {
        protected readonly IList<WorkerThread> _workerThreads = new List<WorkerThread>();
        private int _numberOfWorkerThreads = 1;

        public IObjectBuilder ObjectBuilder { get; set; }
        public IMessageSerializer MessageSerializer { get; set; }

        public int MaxRetries { get; set; }

        public event EventHandler MessageProcessingStarted;
        public event EventHandler MessageProcessingFinished;
        public event EventHandler<MessageProcessingErrorEventArgs> MessageProcessingError;


        #region ITransport Members

        public event EventHandler<MessageReceivedEventArgs> MessageReceived;

        public virtual string ErrorAddress
        {
            get { return Endpoint.Metadata.PoisonMessageAddress; }
        }

        public virtual string ReturnAddress
        {
            get { return Endpoint.Metadata.ReturnAddress;}
        }

        public bool IsSendOnly
        {
            get
            {
                if (Endpoint != null && Endpoint.Metadata != null)
                    return Endpoint.Metadata.SendOnly;
                return false;
            }
        }

        
        protected override void StartService()
        {
            if (IsSendOnly)
                return;

            for (int i = 0; i < _numberOfWorkerThreads; i++)
                AddWorkerThread().Start();
        }

        protected override void ShutDownService()
        {
            lock (_workerThreads)
                for (int i = 0; i < _workerThreads.Count; i++)
                    _workerThreads[i].Stop();
        }

        public void ChangeNumberOfWorkerThreads(int targetNumberOfWorkerThreads)
        {
            lock (_workerThreads)
            {
                _numberOfWorkerThreads = targetNumberOfWorkerThreads;
                
                if (IsRunning == false) return;

                int current = _workerThreads.Count;

                if (targetNumberOfWorkerThreads == current)
                    return;

                if (targetNumberOfWorkerThreads < current)
                {
                    for (int i = targetNumberOfWorkerThreads; i < current; i++)
                        _workerThreads[i].Stop();

                    return;
                }

                if (targetNumberOfWorkerThreads > current)
                {
                    for (int i = current; i < targetNumberOfWorkerThreads; i++)
                        AddWorkerThread().Start();

                    return;
                }
            }
        }

        public abstract void Send(MessageEnvelope m, params string[] destination);
        

        public virtual void ReceiveMessageLater(MessageEnvelope m)
        {
            Send(m, ReturnAddress);
        }
        
        public virtual int NumberOfWorkerThreads
        {
            get
            {
                if (IsRunning)
                {
                    lock (_workerThreads)
                        return _workerThreads.Count;
                }
                return _numberOfWorkerThreads;
            }
            set
            {
                ChangeNumberOfWorkerThreads(value);
            }
        }

        //public virtual string Address { get; set; }

        public abstract int GetNumberOfPendingMessages();

        #endregion

        public virtual MessageEnvelope CreateEmptyMessageEnvelope()
        {
            var message = new MessageEnvelope();
            return message;
        }


        protected virtual void Receive()
        {
            MessageEnvelope messageEnvelope = null;
            try
            {
                OnStartedMessageProcessing();

                messageEnvelope = GetNextMessage();

                if (messageEnvelope != null)
                    OnTransportMessageReceived(new MessageReceivedEventArgs(messageEnvelope));

                OnFinishedMessageProcessing();
            }
            catch (Exception ex)
            {
                //If we are succesfull in notifying someone about this error,
                //we'll go on our merry old way, assuming that it has been dealt with.
                //Otherwise, we'll re-throw, maybe someone down the line can help.
                if(!OnMessageProcessingError(ex,messageEnvelope))
                    throw;
            }
        }
     
        protected WorkerThread AddWorkerThread()
        {
            lock (_workerThreads)
            {
                var result = new WorkerThread(Receive);

                _workerThreads.Add(result);

                result.Stopped += delegate(object sender, EventArgs e)
                                      {
                                          var wt = sender as WorkerThread;
                                          lock (_workerThreads)
                                              _workerThreads.Remove(wt);
                                      };

                return result;
            }
        }

        protected virtual void OnTransportMessageReceived(MessageReceivedEventArgs args)
        {
            EventHandler<MessageReceivedEventArgs> evt = MessageReceived;
            if (evt != null)
                evt(this, args);
        }

        protected virtual void OnStartedMessageProcessing()
        {
            if (MessageProcessingStarted != null)
                MessageProcessingStarted(this, new EventArgs());
        }

        protected virtual void OnFinishedMessageProcessing()
        {
            if (MessageProcessingFinished != null)
                MessageProcessingFinished(this, new EventArgs());
        }

        protected virtual bool OnMessageProcessingError(Exception error,MessageEnvelope message)
        {
            var evt = MessageProcessingError;
            if (evt != null)
            {
                MessageProcessingError(this, new MessageProcessingErrorEventArgs{Exception = error,Message = message});
                return true;
            }
            return false;
        }

        protected abstract MessageEnvelope GetNextMessage();
    
      

    }
}